package model

import (
	"context"
	"fmt"
	"os/user"
	"tk-gen/model/loader"
	"tk-gen/model/models"
	"tk-gen/types"

	"github.com/xo/dburl"
	"github.com/xo/dburl/passfile"

	// drivers
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
	_ "github.com/mattn/go-sqlite3"
	_ "github.com/microsoft/go-mssqldb"
	_ "github.com/sijms/go-ora/v2"
)

// buildContext builds a context for the mode and template.
func buildContext(ctx context.Context, args *types.Args) context.Context {
	// add loader flags
	for k, v := range args.LoaderParams.Flags {
		ctx = context.WithValue(ctx, k, v)
	}
	// add template flags
	for k, v := range args.TemplateParams.Flags {
		ctx = context.WithValue(ctx, k, v)
	}
	// add out
	ctx = context.WithValue(ctx, types.OutKey, args.OutParams.Out)
	ctx = context.WithValue(ctx, types.SingleKey, args.OutParams.Single)
	return ctx
}

// open opens a connection to the database, returning a context for use in
// template generation.
func open(ctx context.Context, urlstr, schema string) (context.Context, error) {
	v, err := user.Current()
	if err != nil {
		return nil, err
	}
	// parse dsn
	u, err := dburl.Parse(urlstr)
	if err != nil {
		return nil, err
	}
	// open database
	db, err := passfile.OpenURL(u, v.HomeDir, "xopass")
	if err != nil {
		return nil, err
	}
	// add driver to context
	ctx = context.WithValue(ctx, types.DriverKey, u.Driver)
	// add db to context
	ctx = context.WithValue(ctx, types.DbKey, db)
	// determine schema
	if schema == "" {
		if schema, err = loader.Schema(ctx); err != nil {
			return nil, err
		}
	}
	// add schema to context
	ctx = context.WithValue(ctx, types.SchemaKey, schema)
	return ctx, nil
}

func Load(ctx context.Context, mode string, dsn string, args *types.Args) (*types.Set, error) {
	var err error
	// build context
	ctx = buildContext(ctx, args)
	// enable verbose output for sql queries
	if args.Verbose {
		models.SetLogger(func(str string, v ...any) {
			s, z := "SQL: %s\n", []any{str}
			if len(v) != 0 {
				s, z = s+"PARAMS: %v\n", append(z, v)
			}
			fmt.Printf(s+"\n", z...)
		})
	}
	// open database
	if ctx, err = open(ctx, dsn, args.LoaderParams.Schema); err != nil {
		return nil, err
	}
	// load loads a set of queries or schemas.
	f := loadSchema
	if mode == "query" {
		f = loadQuery
	}
	set := new(types.Set)
	if err := f(ctx, set, args); err != nil {
		return nil, err
	}
	return set, nil
}
